package com.seckill.trade.service.impl;

import cn.hutool.core.bean.copier.CopyOptions;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.seckill.api.client.item.ItemClient;
import com.seckill.api.client.promotion.PromotionClient;
import com.seckill.api.client.user.UserClient;
import com.seckill.api.constants.ItemStatus;
import com.seckill.api.dto.item.ItemSimpleInfoDTO;
import com.seckill.api.dto.promotion.CouponDiscountDTO;
import com.seckill.api.dto.promotion.OrderCouponDTO;
import com.seckill.api.dto.promotion.OrderItemDTO;
import com.seckill.api.dto.trade.OrderBasicDTO;
import com.seckill.common.autoconfigure.mq.RabbitMqHelper;
import com.seckill.common.autoconfigure.redisson.annotations.Lock;
import com.seckill.common.constants.MqConstants;
import com.seckill.common.exceptions.BadRequestException;
import com.seckill.common.exceptions.BizIllegalException;
import com.seckill.common.exceptions.DbException;
import com.seckill.common.utils.BeanUtils;
import com.seckill.common.utils.CollUtils;
import com.seckill.common.utils.UserContext;
import com.seckill.trade.domian.dto.PayOrderFormDTO;
import com.seckill.trade.domian.dto.PlaceOrderDTO;
import com.seckill.trade.domian.dto.SecOrderDTO;
import com.seckill.trade.domian.po.ItemSeckill;
import com.seckill.trade.domian.po.Order;
import com.seckill.trade.domian.po.OrderDetail;
import com.seckill.trade.domian.query.OrderRequestQuery;
import com.seckill.trade.domian.vo.OrderConfirmVO;
import com.seckill.trade.domian.vo.OrderItemVO;
import com.seckill.trade.domian.vo.PlaceOrderResultVO;
import com.seckill.trade.mapper.OrderMapper;
import com.seckill.trade.service.IOrderDetailService;
import com.seckill.trade.service.IOrderService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.seckill.trade.websocket.WebSocketServer;
import io.seata.spring.annotation.GlobalTransactional;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import com.seckill.trade.constants.redisConstants;
import org.springframework.transaction.annotation.Transactional;

import java.time.Duration;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * <p>
 * 订单表 服务实现类
 * </p>
 *
 * @author zsc
 * @since 2025-05-20
 */
@Service
@RequiredArgsConstructor
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements IOrderService {

    private final StringRedisTemplate redisTemplate;

    private final RabbitMqHelper mqHelper;

    private final ItemClient itemClient;

    private final PromotionClient promotionClient;

    private final IOrderDetailService orderDetailService;

    private final UserClient userClient;

    private final WebSocketServer webSocketServer;


    @Override
    public OrderConfirmVO createOrder(OrderRequestQuery orderRequestQuery) {
        //1.校验商品库存
        //1.1 查询商品信息
        List<ItemSimpleInfoDTO> itemInfos = itemClient.getSimpleInfoList(orderRequestQuery.getItemId());
        if(CollUtils.isEmpty(itemInfos)){
            throw new BadRequestException("商品不存在");
        }
        List<OrderItemVO> items = BeanUtils.copyList(orderRequestQuery.getItemId(), OrderItemVO.class);

        //2算总价
        int total = itemInfos.stream().mapToInt(ItemSimpleInfoDTO::getPrice).sum();

        // 3.计算折扣
        List<OrderItemDTO> orderCourses = itemInfos.stream()
                .map(ci -> new OrderItemDTO().setId(ci.getId()).setCateId(ci.getThirdCateId()).setPrice(ci.getPrice()))
                .collect(Collectors.toList());
        List<CouponDiscountDTO> discountSolution = promotionClient.findDiscountSolution(orderCourses);

        // 4.生成订单id
        long orderId = IdWorker.getId();

        // 5.组织返回
        OrderConfirmVO vo = new OrderConfirmVO();
        vo.setOrderId(orderId);
        vo.setTotalAmount(total);
        vo.setDiscounts(discountSolution);
        vo.setItems(items);

        return vo;

    }

    @Override
    //加锁或者lua脚本保证库存扣减的原子性
    @Lock(name = "lock:hotspot:#{T(com.seckill.common.utils.UserContext).getUser()}")
    public void seckillOrder(OrderRequestQuery orderRequestQuery) {
        //1.查询redis中商品库存
        String key = redisConstants.HOTSPOT_CACHE_KEY_PREFIX + orderRequestQuery.getSeckillItemId();
        Map<Object, Object> objMap = redisTemplate.opsForHash().entries(key);
        if (objMap.isEmpty()) {
            return;
        }
        ItemSeckill itemSeckill = BeanUtils.mapToBean(objMap, ItemSeckill.class,false, CopyOptions.create());
        if(itemSeckill == null){
            throw new BadRequestException("商品不存在");
        }

        //2.检验发放时间
        LocalDateTime now = LocalDateTime.now();
        if(now.isBefore(itemSeckill.getStartTime()) || now.isAfter(itemSeckill.getEndTime())){
            throw new BadRequestException("秒杀时间未开始或已结束");
        }

        //3.校验库存
        if(itemSeckill.getSeckillStock() <= 0 ){
            throw new BadRequestException("商品库存不足");
        }

        //4.校验每人限领数量
        Long userId = UserContext.getUser();
        //4.1 查询用户已经领取数量
        String userKey = redisConstants.USER_ITEM_CACHE_KEY_PREFIX + orderRequestQuery.getSeckillItemId();
        Long count = redisTemplate.opsForHash().increment(userKey, userId.toString(), 1);
        //4.2 判断是否超过限领数量
        if(count > itemSeckill.getLimitNum()){
            throw new BadRequestException("超出限领数量");
        }


        //5.发送MQ消息实现排队下单

        SecOrderDTO secOrderDTO = new SecOrderDTO();
        secOrderDTO.setUserId(userId);
        secOrderDTO.setItemId(itemSeckill.getItemId());
        secOrderDTO.setItemSeckillId(itemSeckill.getId());
        secOrderDTO.setPrice(itemSeckill.getSeckillPrice());
        secOrderDTO.setQuantity(orderRequestQuery.getQuantity().get(0));
        mqHelper.send(MqConstants.Exchange.TRADE_EXCHANGE , MqConstants.Key.SEC_KILL_KEY, secOrderDTO);
    }

    @Override
    @Transactional
    public PlaceOrderResultVO placeOrder(PlaceOrderDTO placeOrderDTO) {
        Long userId = UserContext.getUser();
        // 1.查询商品费用信息，如果不可购买，这里直接报错
        List<ItemSimpleInfoDTO> itemInfos = getOnShelfCourse(placeOrderDTO.getItemIds());
        // 2.封装订单信息
        Order order = new Order();
        // 2.1.计算订单金额
        Integer totalAmount = itemInfos.stream()
                .map(ItemSimpleInfoDTO::getPrice).reduce(Integer::sum).orElse(0);
        // 2.2.计算优惠金额
        order.setDiscountAmount(0);
        List<Long> couponIds = placeOrderDTO.getCouponIds();
        CouponDiscountDTO discount = null;
        if (CollUtils.isNotEmpty(couponIds)) {
            List<OrderItemDTO> orderCourses = itemInfos.stream()
                    .map(c -> new OrderItemDTO().setId(c.getId()).setCateId(c.getThirdCateId()).setPrice(c.getPrice()))
                    .collect(Collectors.toList());
            discount = promotionClient.queryDiscountDetailByOrder(new OrderCouponDTO(couponIds, orderCourses));
            if(discount != null) {
                order.setDiscountAmount(discount.getDiscountAmount());
                order.setCouponIds(discount.getIds());
            }
        }
        Integer realAmount = totalAmount - order.getDiscountAmount();
        // 2.3.封装其它信息
        order.setUserId(userId);
        order.setTotalAmount(totalAmount);
        order.setRealAmount(realAmount);
        order.setStatus(1);
        // 2.4.订单id
        Long orderId = placeOrderDTO.getOrderId();
        order.setId(orderId);

        // 3.封装订单详情
        List<OrderDetail> orderDetails = new ArrayList<>(itemInfos.size());
        for (ItemSimpleInfoDTO itemInfo : itemInfos) {
            Integer discountValue = discount == null ?
                    0 : discount.getDiscountDetail().getOrDefault(itemInfo.getId(), 0);
            orderDetails.add(packageOrderDetail(itemInfo, order, discountValue));
        }

        // 4.写入数据库
        saveOrderAndDetails(order, orderDetails);


        // 5.核销优惠券
        promotionClient.writeOffCoupon(couponIds);

        // 6.发送延迟消息，检测订单支付状态
        mqHelper.sendDelayMessage(MqConstants.Exchange.TRADE_DELAY_EXCHANGE, MqConstants.Key.ORDER_PAY_KEY,
                order, Duration.ofMinutes(30));

        // 7.构建下单结果
        return PlaceOrderResultVO.builder()
                .orderId(orderId)
                .payAmount(realAmount)
                .status(order.getStatus())
                .payOutTime(LocalDateTime.now().plusMinutes(30))
                .build();
    }

    @Override
    @GlobalTransactional
    @Lock(name = "lock:hotspot:#{secOrderDTO.getUserId()}")
    public void processSecOrder(SecOrderDTO secOrderDTO) {
        //从redis中获取商品信息
        String key = redisConstants.HOTSPOT_CACHE_KEY_PREFIX + secOrderDTO.getItemSeckillId();
        Map<Object, Object> objMap = redisTemplate.opsForHash().entries(key);
        if (objMap.isEmpty()) {
            return;
        }
        ItemSeckill itemSeckill = BeanUtils.mapToBean(objMap, ItemSeckill.class,false, CopyOptions.create());
        //1.封装订单信息
        Order order = new Order();
        order.setUserId(secOrderDTO.getUserId());
        order.setTotalAmount(secOrderDTO.getPrice());
        order.setRealAmount(secOrderDTO.getPrice());
        order.setStatus(1);
        //2.封装订单详情
        OrderDetail orderDetail = new OrderDetail();
        orderDetail.setUserId(secOrderDTO.getUserId());
        orderDetail.setItemId(secOrderDTO.getItemId());
        orderDetail.setPrice(secOrderDTO.getPrice());
        //3.写入数据库
        saveOrderAndDetails(order, List.of(orderDetail));
        //4.扣款
        userClient.deductMoney(secOrderDTO.getPrice());
        //5.修改order状态
        order.setStatus(2);
        order.setPayTime(LocalDateTime.now());
        updateById(order);
        //5.扣减库存
        redisTemplate.opsForHash().increment(key, "seckillStock", -1);
        //6.发送mq消息减少库存
        OrderBasicDTO  orderBasicDTO = OrderBasicDTO.builder()
                .orderId(order.getId())
                .userId(order.getUserId())
                .itemIds(List.of(orderDetail.getItemId()))
                .finishTime(LocalDateTime.now())
                .build();
        mqHelper.send(MqConstants.Exchange.ORDER_EXCHANGE, MqConstants.Key.ORDER_PAY_KEY, orderBasicDTO);

        // 7.发送websocket消息给用户
        //封装消息
        Map msg = new HashMap<>();
        msg.put("orderId", order.getId());
        msg.put("userId", order.getUserId());
        msg.put("itemId", orderDetail.getItemId());
        //发送消息，封装为json
        String msgStr = JSON.toJSONString(msg);
        webSocketServer.sendToAllClient(msgStr);
    }

    @Override
    public void cancelOrder(Long orderId) {
        // 查询订单
        Order order = getById(orderId);
        if(order == null || order.getStatus() == 5){
            // 不做处理
            return;
        }
        // 取消订单
        order.setStatus(5);
        updateById(order);
    }

    @Override
    public void payOrder(PayOrderFormDTO payOrderFormDTO) {
        //  1.查询订单
        Order order = getById(payOrderFormDTO.getId());
        if (order == null || order.getStatus() != 1) {
            // 不做处理
            return;
        }
        List<OrderDetail> orderDetails = orderDetailService.lambdaQuery().eq(OrderDetail::getOrderId, order.getId()).list();
        // 2.支付订单，扣减余额
        userClient.deductMoney(payOrderFormDTO.getPw(),order.getRealAmount());
        // 3.更新订单状态
        order.setStatus(2);
        updateById(order);
        // 4.发送mq消息，修改库存
        OrderBasicDTO  orderBasicDTO = OrderBasicDTO.builder()
               .orderId(order.getId())
               .userId(order.getUserId())
                .itemIds(orderDetails.stream().map(OrderDetail::getItemId).collect(Collectors.toList()))
                .finishTime(LocalDateTime.now())
                .build();

        mqHelper.send(MqConstants.Exchange.ORDER_EXCHANGE, MqConstants.Key.ORDER_PAY_KEY, orderBasicDTO);

    }

    private void saveOrderAndDetails(Order order, List<OrderDetail> orderDetails) {
        // 4.1.写订单
        boolean success = save(order);
        if (!success) {
            throw new DbException("下单失败");
        }
        orderDetails.forEach(od -> od.setOrderId(order.getId()));
        //收集订单iD
        List<Long> itemIds = orderDetails.stream().map(OrderDetail::getItemId).collect(Collectors.toList());
        //todo
        itemIds.add(1L);
        List<ItemSimpleInfoDTO> simpleInfoList = itemClient.getSimpleInfoList(itemIds);
        //转化为map,key为itemId,value为ItemSimpleInfoDTO
        Map<Long, ItemSimpleInfoDTO> itemSimpleInfoDTOMap = simpleInfoList.stream().collect(Collectors.toMap(ItemSimpleInfoDTO::getId, item -> item));
        orderDetails.forEach(od -> {
             od.setName(itemSimpleInfoDTOMap.get(od.getItemId()).getName());
             od.setImage(itemSimpleInfoDTOMap.get(od.getItemId()).getImage());
             od.setPrice(itemSimpleInfoDTOMap.get(od.getItemId()).getPrice());

        });

        // 4.2.写订单详情
        if(orderDetails.size() == 1){
            success = orderDetailService.save(orderDetails.get(0));
        }else {
            success = orderDetailService.saveBatch(orderDetails);
        }
        if (!success) {
            throw new DbException("下单失败");
        }
    }

    private OrderDetail packageOrderDetail(ItemSimpleInfoDTO itemInfo, Order order, Integer discountValue) {
        OrderDetail detail = new OrderDetail();
        detail.setUserId(order.getUserId());
        detail.setOrderId(order.getId());
        detail.setItemId(itemInfo.getId());
        detail.setPrice(itemInfo.getPrice());
        detail.setImage(itemInfo.getImage());
        detail.setName(itemInfo.getName());
        return detail;
    }

    private List<ItemSimpleInfoDTO> getOnShelfCourse(List<Long> itemIds) {
        // 1.查询商品
        List<ItemSimpleInfoDTO> itemInfos = itemClient.getSimpleInfoList(itemIds);
        LocalDateTime now = LocalDateTime.now();
        // 2.判断状态
        for (ItemSimpleInfoDTO itemInfo : itemInfos) {
            // 2.1.检查商品是否上架
            if(!ItemStatus.SHELF.equalsValue(itemInfo.getStatus())){
                throw new BizIllegalException("商品未上架");
            }
        }
        return itemInfos;
    }
}
